package sidecar

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	su "lqq/sidecar/utils"
	"net/http"
	"strconv"
	"sync"
	"time"

	CMap "github.com/orcaman/concurrent-map"
)

// 注册服务
type registerServer interface {
	register(serviceName string, addr string) error
	getAddrs(serviceName string) ([]string, error)
	putErrInstances(inst registerServerInst)
}

type nacosRegisterServer struct {
	httpClient        *http.Client
	registerReqChan   chan *registerReqData
	getAddrsReqChan   chan *getAddrsReqData
	errInstChan       chan *nacosRegisterServerInst
	mu                sync.Mutex
	cuIndex           int
	insts             []*nacosRegisterServerInst
	registeringReqMap CMap.ConcurrentMap
	getAddrsingReqMap CMap.ConcurrentMap
}

func (s *nacosRegisterServer) putErrInstances(inst *nacosRegisterServerInst) {
	s.errInstChan <- inst
}

type registerServerInst interface {
	reqeustRegister(r *registerReqData)
	reqeustGetAddrs(r *getAddrsReqData)
}

type nacosRegisterServerInst struct {
	addr            string
	parent          *nacosRegisterServer
	instID          string
	canUse          bool
	registerReqChan chan *registerReqData
	getAddrsReqChan chan *getAddrsReqData
}

func newNacosRegisterServerInst(nacosInstAddr string, parent *nacosRegisterServer) *nacosRegisterServerInst {
	s := &nacosRegisterServerInst{
		addr:            nacosInstAddr,
		parent:          parent,
		instID:          su.UUID(),
		canUse:          true,
		registerReqChan: make(chan *registerReqData, 3),
		getAddrsReqChan: make(chan *getAddrsReqData, 3),
	}
	go s.monitor()
	return s
}

func (s *nacosRegisterServerInst) monitor() {
	for {
		select {
		case r := <-s.registerReqChan:
			go s.reqeustRegister(r)
		case r := <-s.getAddrsReqChan:
			go s.reqeustGetAddrs(r)
		}
	}
}

func (s *nacosRegisterServerInst) reqeustRegister(r *registerReqData) {
	/*
		服务注册
		curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.10&port=8080'

		服务发现
		curl -X GET 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=nacos.naming.serviceName'

		发布配置
		curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test&content=HelloWorld"

		获取配置
		curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"
	*/
	p := strconv.Itoa(r.port)
	regString := "serviceName=" + r.serviceName + "&ip=" + r.ip + "&port=" + p
	resp, err := s.parent.httpClient.Post("http://"+s.addr+"/nacos/v1/ns/instance", "application/x-www-form-urlencoded", bytes.NewReader([]byte(regString)))
	if err != nil {
		logger.error(err.Error())
		s.parent.errInstChan <- s
	}
	resp.Body.Close()
}

type nacosServiceMsg struct {
	Name      string      `json:name`
	GroupName string      `json:groupName`
	Hosts     []nacosHost `json:hosts`
}

type nacosHost struct {
	InstanceId string  `json:instanceId`
	IP         string  `json:ip`
	Port       int     `json:port`
	Weight     float32 `json:weight`
	Healthy    bool    `json:healthy`
	Enabled    bool    `json:enabled`
}

func (s *nacosRegisterServerInst) reqeustGetAddrs(r *getAddrsReqData) {
	refreshURL := "http://" + s.addr + "/nacos/v1/ns/instance/list?serviceName=" + r.serviceName
	resp, err := s.parent.httpClient.Get(refreshURL)
	if err != nil {
		logger.error(err.Error())
		s.parent.errInstChan <- s
		r.ok = false
		r.result = make([]string, 0)
		r.segin <- true
		return
	}
	if resp.StatusCode != 200 {
		r.ok = false
		r.result = make([]string, 0)
		r.segin <- true
		return
	}
	defer resp.Body.Close()
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		r.ok = false
		r.result = make([]string, 0)
		r.segin <- true
		return
	}
	nacosMsg := &nacosServiceMsg{}
	err = json.Unmarshal(respBody, nacosMsg)
	if err != nil {
		r.ok = false
		r.result = make([]string, 0)
		r.segin <- true
		return
	}
	r.ok = true
	sList := make([]string, 0)
	for _, l := range nacosMsg.Hosts {
		if l.Enabled && l.Healthy {
			s := l.IP + ":" + strconv.Itoa(l.Port)
			sList = append(sList, s)
		}
	}
	r.result = sList
	r.segin <- true

}

type registerReqData struct {
	serviceName string
	ip          string
	port        int
	segin       chan bool
	ok          bool
}

type getAddrsReqData struct {
	serviceName string
	segin       chan bool
	ok          bool
	result      []string
}

func newNacosRegisterServer(nacosAddrs []string) *nacosRegisterServer {
	nacosInsts := make([]*nacosRegisterServerInst, 0)
	s := &nacosRegisterServer{
		httpClient:        &http.Client{},
		mu:                sync.Mutex{},
		errInstChan:       make(chan *nacosRegisterServerInst, 1),
		cuIndex:           0,
		insts:             nacosInsts,
		registerReqChan:   make(chan *registerReqData, 5),
		getAddrsReqChan:   make(chan *getAddrsReqData, 5),
		registeringReqMap: CMap.New(),
		getAddrsingReqMap: CMap.New(),
	}

	for _, nocosAddr := range nacosAddrs {
		nacosInsts = append(nacosInsts, newNacosRegisterServerInst(nocosAddr, s))
	}
	s.insts = nacosInsts
	go s.monitor()
	return s
}

func (n *nacosRegisterServer) monitor() {
	for {
		select {
		case registerData := <-n.registerReqChan:
			sn, ok := n.registeringReqMap.Get(registerData.serviceName)
			if ok {
				t := sn.(time.Time)
				if time.Now().Sub(t) < 3*time.Second {
					registerData.ok = true
					registerData.segin <- true
					continue
				} else {
					n.registeringReqMap.Remove(registerData.serviceName)
				}
			}
			n.registeringReqMap.Set(registerData.serviceName, time.Now())
			cuInst, instOK := n.getOneInstance()
			if instOK {
				cuInst.registerReqChan <- registerData
			} else {
				registerData.ok = false
				registerData.segin <- true
			}

		case getAddrsData := <-n.getAddrsReqChan:
			sn, ok := n.getAddrsingReqMap.Get(getAddrsData.serviceName)
			if ok {
				t := sn.(time.Time)
				if time.Now().Sub(t) < 3*time.Second {
					getAddrsData.ok = true
					getAddrsData.result = make([]string, 0)
					getAddrsData.segin <- true
					continue
				} else {
					n.getAddrsingReqMap.Remove(getAddrsData.serviceName)
				}
			}
			n.getAddrsingReqMap.Set(getAddrsData.serviceName, time.Now())
			cuInst, instOK := n.getOneInstance()
			if instOK {
				cuInst.getAddrsReqChan <- getAddrsData
			} else {
				getAddrsData.ok = false
				getAddrsData.result = make([]string, 0)
				getAddrsData.segin <- true
			}
		case r := <-n.errInstChan:
			n.mu.Lock()
			r.canUse = false
			for i := 0; i < len(n.insts); i++ {
				if n.insts[i] == r {
					n.insts = append(n.insts[:i], n.insts[i+1:]...)
					break
				}
			}
			n.mu.Unlock()
		}
	}
}
func (n *nacosRegisterServer) getOneInstance() (*nacosRegisterServerInst, bool) {
	defer n.mu.Unlock()
	n.mu.Lock()
	var cuInst *nacosRegisterServerInst
	instOK := false
	for len(n.insts) > 0 {
		n.cuIndex++
		if n.cuIndex > len(n.insts)-1 {
			n.cuIndex = 0
		}
		cuInst = n.insts[n.cuIndex]
		instOK = true
		if !cuInst.canUse {
			instOK = false
			n.insts = append(n.insts[0:n.cuIndex], n.insts[n.cuIndex+1:]...)
		}
		if instOK {
			break
		}
	}
	return cuInst, instOK
}

func (n *nacosRegisterServer) register(serviceName string, ip string, port int) error {
	reqData := &registerReqData{
		serviceName: serviceName,
		ip:          ip,
		port:        port,
		segin:       make(chan bool),
	}
	n.registerReqChan <- reqData
	select {
	case <-reqData.segin:
		if reqData.ok {
			return nil
		} else {
			return errors.New("")
		}
	case <-time.After(5 * time.Second):
		return errors.New("")
	}
}

func (n *nacosRegisterServer) getAddrs(serviceName string) ([]string, error) {
	reqData := &getAddrsReqData{
		serviceName: serviceName,
		segin:       make(chan bool),
		ok:          false,
		result:      make([]string, 0),
	}
	n.getAddrsReqChan <- reqData
	select {
	case <-reqData.segin:
		if reqData.ok {
			return reqData.result, nil
		} else {
			return nil, errors.New("")
		}
	case <-time.After(5 * time.Second):
		return nil, errors.New("")
	}

}
